// -----------------------------------------------------------------------
//  <copyright file="UnitOfWork.cs" company="LiuliuSoft">
//      Copyright (c) 2022-2025 DaprPlus. All rights reserved.
//  </copyright>
//  <site>https://dapr.plus</site>
//  <last-editor>郭明锋</last-editor>
//  <last-date>2025/1/14 16:26</last-date>
// -----------------------------------------------------------------------

using OSharp.Threading;

namespace DaprPlus.EntityFrameworkCore;

/// <summary>
/// 实现一个单元操作的功能，管理单元操作内涉及的所有上下文对象及其事务
/// </summary>
public class UnitOfWork : Disposable, IUnitOfWork
{
    private readonly IServiceProvider _provider;
    private readonly DbContext _dbContext;
    private readonly ILogger<UnitOfWork> _logger;
    private DbTransaction? _transaction;
    private readonly Stack<string> _transactionStack = new();
    private readonly ICancellationTokenProvider _cancellationTokenProvider;
    private readonly List<IntegrationEvent> _integrationEvents = new();

    /// <summary>
    /// 初始化一个<see cref="UnitOfWork"/>类型的新实例
    /// </summary>
    public UnitOfWork(IServiceProvider provider)
    {
        _provider = provider;
        DbContext = provider.GetRequiredService<IDbContext>();
        _dbContext = DbContext as DbContext ?? throw new InvalidOperationException(ResEFCore.String_Entity_MustDbContext);
        _logger = provider.GetLogger<UnitOfWork>();
        _cancellationTokenProvider = provider.GetRequiredService<ICancellationTokenProvider>();
    }

    /// <summary>
    /// 获取 当前单元操作的数据上下文
    /// </summary>
    public IDbContext DbContext { get; }

    /// <summary>
    /// 是否已提交事务
    /// </summary>
    public bool HasCommitted { get; private set; }

    /// <summary>
    /// 是否启用事务
    /// </summary>
    public bool IsEnabledTransaction => _transactionStack.Count > 0;

    /// <summary>
    /// 启用事务，事务代码写在 UnitOfWork.EnableTransaction() 与 UnitOfWork.Commit() 之间
    /// </summary>
    public void EnableTransaction()
    {
        var token = Guid.NewGuid().ToString();
        _transactionStack.Push(token);
        _logger.LogDebug(ResEFCore.Format_Entity_Uow_EnableTransaction, token, _transactionStack.Count);
    }

    /// <summary>
    /// 对数据库连接开启事务
    /// </summary>
    /// <param name="token">异步取消标记</param>
    /// <returns></returns>
    public async Task BeginOrUseTransactionAsync(CancellationToken token = default)
    {
        //如果未显式启用事务，就不开启
        if (!IsEnabledTransaction)
        {
            return;
        }

        token = CancellationToken(token);
        if (_transaction?.Connection == null)
        {
            // 事务不存在，创建事务
            var connection = _dbContext.Database.GetDbConnection();
            if (connection.State != ConnectionState.Open)
            {
                await connection.OpenAsync(token);
            }

            _transaction = await connection.BeginTransactionAsync(token);
            _logger.LogDebug(ResEFCore.Format_Entity_Uow_CreateTransaction, _dbContext.GetType(), _dbContext.GetHashCode(), _transaction.GetHashCode());
        }
        if (_dbContext.Database.CurrentTransaction == null || _dbContext.Database.CurrentTransaction.GetDbTransaction() != _transaction)
        {
            // 事务存在，但是上下文中的事务不是当前事务，将当前事务赋值给上下文
            await _dbContext.Database.UseTransactionAsync(_transaction, token);
            _logger.LogDebug(ResEFCore.Format_Entity_Uow_UseTransaction, _dbContext.GetType(), _dbContext.GetHashCode(), _transaction.GetHashCode());
        }

        HasCommitted = false;
    }

    /// <summary>
    /// 提交当前上下文的事务更改
    /// </summary>
    /// <returns></returns>
    public async Task CommitAsync(CancellationToken token = default)
    {
        if (HasCommitted || _transaction == null)
        {
            return;
        }

        if (!IsEnabledTransaction)
        {
            throw new OsharpException(ResEFCore.String_Entity_Uow_CheckTransaction);
        }

        string transToken;
        if (_transactionStack.Count > 1)
        {
            transToken = _transactionStack.Pop();
            _logger.LogDebug(ResEFCore.Format_Entity_Uow_SkipTransaction, transToken, _transactionStack.Count);
            return;
        }

        transToken = _transactionStack.Pop();

        token = CancellationToken(token);
        await _transaction.CommitAsync(token);
        _logger.LogDebug(ResEFCore.Format_Entity_Uow_CommitTransaction, transToken, _transaction.GetHashCode());

        // 在事务提交成功之后，对服务间发送模型中的集成事件
        await DispatchIntegrationEventsAsync(token);

        HasCommitted = true;
    }

    /// <summary>
    /// 回滚所有事务
    /// </summary>
    /// <returns></returns>
    public async Task RollbackAsync(CancellationToken token = default)
    {
        if (_transaction?.Connection == null)
        {
            return;
        }
        token = CancellationToken(token);
        await _transaction.RollbackAsync(token);
        _logger.LogDebug(ResEFCore.Format_Entity_Uow_RollbackTransaction, _transaction.GetHashCode());
        HasCommitted = true;
    }

    /// <summary>
    /// 添加集成事件，这些事件将在工作单元提交事务后发布
    /// </summary>
    /// <param name="events">集成事件</param>
    public void AddIntegrationEvent(params IntegrationEvent[] events)
    {
        foreach (var @event in events)
        {
            _integrationEvents.AddIfNotExist(@event);
        }
    }

    // 发送集成事件，将事件总线收集的领域事件发送出去
    protected virtual async Task DispatchIntegrationEventsAsync(CancellationToken token)
    {
        if (_integrationEvents.Count == 0)
        {
            return;
        }
        var integrationEventBus = _provider.GetService<IIntegrationEventBus>();
        if (integrationEventBus == null)
        {
            return;
        }
        var events = _integrationEvents.ToList();
        if (events.Count > 0)
        {
            _integrationEvents.Clear();
            foreach (var integrationEvent in events)
            {
                await integrationEventBus.PublishAsync(integrationEvent, token);
            }
        }
    }

    private CancellationToken CancellationToken(CancellationToken token)
    {
        return token != default ? token : _cancellationTokenProvider.Token;
    }

    /// <summary>
    /// 重写以实现释放派生类资源的逻辑
    /// </summary>
    protected override void Disposing()
    {
        _dbContext.Dispose();
    }
}
